package com.swak.frame.swagger.spring.configuration;

import java.lang.reflect.Field;
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;

import com.github.xiaoymin.knife4j.core.extend.OpenApiExtendSetting;
import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
import com.github.xiaoymin.knife4j.spring.filter.ProductionSecurityFilter;
import com.github.xiaoymin.knife4j.spring.filter.SecurityBasicAuthFilter;
import com.swak.frame.SwakConstants;
import com.swak.frame.Version;
import com.swak.frame.dto.base.SysRestCode;
import com.swak.frame.swagger.config.SwakApiInfo;
import com.swak.frame.swagger.config.SwakKnife4jProperties;
import com.swak.frame.swagger.constants.SwaggerConstant;
import com.swak.frame.util.GetterUtil;

import io.swagger.annotations.ApiOperation;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Response;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;

@Configuration
@EnableConfigurationProperties({ SwakKnife4jProperties.class })
@ComponentScan(
        basePackages = {
                "com.github.xiaoymin.knife4j.spring.plugin",
        }
)
@ConditionalOnProperty(prefix = SwakConstants.SWAK_SWAGGER, name = "enable", havingValue = "true", matchIfMissing = false)
@EnableWebMvc
public class SwaggerAutoConfiguration {

	@Autowired
	private Environment environment;

	private Logger logger = LoggerFactory.getLogger(SwaggerAutoConfiguration.class);
	
	
	@Bean
	public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
	    return new BeanPostProcessor() {
	        @Override
	        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
	            if (bean instanceof WebMvcRequestHandlerProvider ) {
	                customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
	            }
	            return bean;
	        }

	        private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
	            List<T> copy = mappings.stream()
	                .filter(mapping -> mapping.getPatternParser() == null)
	                .collect(Collectors.toList());
	            mappings.clear();
	            mappings.addAll(copy);
	        }

	        @SuppressWarnings("unchecked")
	        private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
	            try {
	                Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
	                field.setAccessible(true);
	                return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
	            } catch (IllegalArgumentException | IllegalAccessException e) {
	                throw new IllegalStateException(e);
	            }
	        }
	    };
	}

	/**
	 * 配置Cors
	 * 
	 * @since 2.0.4
	 * @return
	 */
	@Bean("knife4jCorsFilter")
	@ConditionalOnMissingBean(CorsFilter.class)
	@ConditionalOnProperty(prefix = SwakConstants.SWAK_SWAGGER, name = "cors", havingValue = "true")
	public CorsFilter corsFilter() {
		logger.info("init CorsFilter...");
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		CorsConfiguration corsConfiguration = new CorsConfiguration();
		corsConfiguration.setAllowCredentials(true);
		//corsConfiguration.addAllowedOrigin("*");
		corsConfiguration.addAllowedOriginPattern("*");
		corsConfiguration.addAllowedHeader("*");
		corsConfiguration.addAllowedMethod("*");
		corsConfiguration.setMaxAge(10000L);
		// 匹配所有API
		source.registerCorsConfiguration("/**", corsConfiguration);
		CorsFilter corsFilter = new CorsFilter(source);
		return corsFilter;
	}

	@Bean(initMethod = "start")
	@ConditionalOnMissingBean(OpenApiExtensionResolver.class)
	@ConditionalOnProperty(prefix = SwakConstants.SWAK_SWAGGER, name = "enable", havingValue = "true")
	public OpenApiExtensionResolver markdownResolver(SwakKnife4jProperties knife4jProperties) {
		OpenApiExtendSetting setting = knife4jProperties.getSetting();
		if (setting == null) {
			setting = new OpenApiExtendSetting();
		}
		return new OpenApiExtensionResolver(setting, knife4jProperties.getDocuments());
	}

	@Bean
	@ConditionalOnMissingBean(SecurityBasicAuthFilter.class)
	@ConditionalOnProperty(prefix = SwaggerConstant.SWAGGER_BASIC, name = "enable", havingValue = "true")
	public SecurityBasicAuthFilter securityBasicAuthFilter(SwakKnife4jProperties knife4jProperties) {
		boolean enableSwaggerBasicAuth = false;
		String dftUserName = "admin", dftPass = "123321";
		SecurityBasicAuthFilter securityBasicAuthFilter = null;
		if (knife4jProperties == null) {
			if (environment != null) {
				String enableAuth = environment.getProperty(SwaggerConstant.SWAGGER_BASIC + ".enable");
				enableSwaggerBasicAuth = Boolean.valueOf(enableAuth);
				if (enableSwaggerBasicAuth) {
					// 如果开启basic验证,从配置文件中获取用户名和密码
					String pUser = environment.getProperty(SwaggerConstant.SWAGGER_BASIC + ".username");
					String pPass = environment.getProperty(SwaggerConstant.SWAGGER_BASIC + ".password");
					if (pUser != null && !"".equals(pUser)) {
						dftUserName = pUser;
					}
					if (pPass != null && !"".equals(pPass)) {
						dftPass = pPass;
					}
				}
				securityBasicAuthFilter = new SecurityBasicAuthFilter(enableSwaggerBasicAuth, dftUserName, dftPass);
			}
		} else {
			// 判断非空
			if (knife4jProperties.getBasic() == null) {
				securityBasicAuthFilter = new SecurityBasicAuthFilter(enableSwaggerBasicAuth, dftUserName, dftPass);
			} else {
				securityBasicAuthFilter = new SecurityBasicAuthFilter(knife4jProperties.getBasic().isEnable(),
						knife4jProperties.getBasic().getUsername(), knife4jProperties.getBasic().getPassword());
			}
		}
		return securityBasicAuthFilter;
	}

	@Bean
	@ConditionalOnMissingBean(ProductionSecurityFilter.class)
	@ConditionalOnProperty(prefix = SwakConstants.SWAK_SWAGGER, name = "production", havingValue = "true")
	public ProductionSecurityFilter productionSecurityFilter(SwakKnife4jProperties knife4jProperties) {
		boolean prod = false;
		ProductionSecurityFilter p = null;
		if (knife4jProperties == null) {
			if (environment != null) {
				String prodStr = environment.getProperty(SwaggerConstant.SWAGGER_PRODUCTION);
				if (logger.isDebugEnabled()) {
					logger.debug("swagger.production:{}", prodStr);
				}
				prod = Boolean.valueOf(prodStr);
			}
			p = new ProductionSecurityFilter(prod);
		} else {
			p = new ProductionSecurityFilter(knife4jProperties.isProduction());
		}

		return p;
	}

	//生成通用响应信息
    private List<Response> getGlobalResonseMessage() {
        List<Response> responseList = new ArrayList<>();
        responseList.add(new ResponseBuilder().code(SysRestCode.SUCC.getRetCode()).description(SysRestCode.SUCC.getRetDesc()).build());
        responseList.add(new ResponseBuilder().code(SysRestCode.SYS_ERR.getRetCode()).description(SysRestCode.SYS_ERR.getRetDesc()).build());
        responseList.add(new ResponseBuilder().code(SysRestCode.SYS_REQ_LIMIT.getRetCode()).description(SysRestCode.SYS_REQ_LIMIT.getRetDesc()).build());
        responseList.add(new ResponseBuilder().code(SysRestCode.INVALID_WHITE_LIST.getRetCode()).description(SysRestCode.INVALID_WHITE_LIST.getRetDesc()).build());
        responseList.add(new ResponseBuilder().code(SysRestCode.USER_NO_LOGIN.getRetCode()).description(SysRestCode.USER_NO_LOGIN.getRetDesc()).build());
        return responseList;
    }
    
	@Bean
	public Docket createRestApi(SwakKnife4jProperties knife4jProperties,OpenApiExtensionResolver openApiExtensionResolver) {
		String groupName = "V"+Version.CURRENT.toString();
		List<Response>  globalResonseMessage = getGlobalResonseMessage();
		return new Docket(DocumentationType.OAS_30).useDefaultResponseMessages(false).groupName(groupName)
				.enable(!GetterUtil.getBoolean(environment.getProperty(SwaggerConstant.SWAGGER_PRODUCTION)))
				.forCodeGeneration(true).apiInfo(apiInfo(knife4jProperties))
				.useDefaultResponseMessages(false)
				.globalResponses(HttpMethod.GET, globalResonseMessage)
                .globalResponses(HttpMethod.POST, globalResonseMessage)
                .globalResponses(HttpMethod.DELETE, globalResonseMessage)
                .globalResponses(HttpMethod.PATCH, globalResonseMessage)
				.select()
				.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) // 过滤生成链接
				.paths(PathSelectors.any()).build()
				.directModelSubstitute(Temporal.class, String.class)
				
                //.globalResponseMessage(RequestMethod.DELETE, globalResponses)
                //.globalResponseMessage(RequestMethod.PATCH, globalResponses)
				.extensions(openApiExtensionResolver.buildExtensions(groupName));
	}

	private ApiInfo apiInfo(SwakKnife4jProperties knife4jProperties) {
		SwakApiInfo swakApiInfo = knife4jProperties.getInfo();
		String name = StringUtils.firstNonEmpty(swakApiInfo.getContact().getName(), "SWAK研发组");
		String email = StringUtils.firstNonEmpty(swakApiInfo.getContact().getEmail(), "swak@swak.com");
		String url = StringUtils.firstNonEmpty(swakApiInfo.getContact().getUrl(), "");
		return new ApiInfoBuilder().title(StringUtils.firstNonEmpty(swakApiInfo.getTitle(), "SWAK项目API接口文档"))
				.description(StringUtils.firstNonEmpty(swakApiInfo.getDescription(), "SWAK项目API接口详细文档")).version("V"+Version.CURRENT.toString())
				.contact(new Contact(name, url, email)).build();
	}
}
